package com.kl.ccs;

import lombok.extern.slf4j.Slf4j;

import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * CyclicBarrier 是多个线程互相等待
 * CyclicBarrier 是加 1，直到指定值
 * CyclicBarrier  可以循环利用
 * CyclicBarrier 可以在最后一个线程达到屏障之前，选择先执行一个操作
 */
@Slf4j
public class BarrierTest {

    /**
     * 现在模拟一个常用的场景，一组运动员比赛 1000 米，只有在所有人都准备完成之后，才可以一起开跑
     * 定义一个 Runner 类代表运动员，其内部维护一个共有的 CyclicBarrier，每个人都有一个准备时间，
     * 准备完成之后，会调用 await 方法，等待其他运动员。当所有人准备都 OK 时，就可以开跑了
     */
    public static void main(String[] args) {
        // CyclicBarrier barrier = new CyclicBarrier(3);  //①
        // 运动员都准备好后，让裁判吹完口哨之后，他们再一起开跑
        CyclicBarrier barrier = new CyclicBarrier(2, new Runnable() {
            // 计数达到之后，会先执行该回调方法，再唤醒所有阻塞的线程
            @Override
            public void run() {
                try {
                    System.out.println("等裁判吹口哨...");
                    //这里停顿两秒更便于观察线程执行的先后顺序
                    Thread.sleep(2000);
                    System.out.println("裁判吹口哨->>>>>");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        // 可以分两批，第一批先跑两个人，然后第二批再跑两个人，实现了屏障的循环使用
        Runner runner1 = new Runner(barrier, "张三");
        Runner runner2 = new Runner(barrier, "李四");
        Runner runner3 = new Runner(barrier, "王五");
        Runner runner4 = new Runner(barrier, "赵六");

        ExecutorService service = Executors.newFixedThreadPool(3);
        service.execute(runner1);
        service.execute(runner2);
        service.execute(runner3);
        service.execute(runner4);

        service.shutdown();

    }

    static class Runner implements Runnable {

        private final CyclicBarrier barrier;
        private final String name;

        public Runner(CyclicBarrier barrier, String name) {
            this.barrier = barrier;
            this.name = name;
        }

        @Override
        public void run() {
            try {
                // 模拟准备耗时
                Thread.sleep(new Random().nextInt(5000));
                log.info("{}:准备OK", name);
                // 执行完自己的业务逻辑后就阻塞等待其他线程，计数加一
                // 当计数达到指定值后被唤醒，接着执行之后的方法
                barrier.await();
                log.info("{}: 开跑", name);
            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
    }
}